﻿namespace SolidMvvm {
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.IO;
	using System.Reflection;
	using System.Windows;
	using System.Windows.Interop;
	using System.Windows.Media;

	using SolidMvvmInterfaces;

	public class WPFApp : IDisposable, IApplicationContext {
		ILogger logger;

		public WPFApp(Assembly executingAssembly, bool forceSoftwareOnlyRendering, string pathToLaunchResourceDictionary) {
			try {
				ExecutingAssembly = executingAssembly;
				ForceSoftwareOnlyRendering = forceSoftwareOnlyRendering;

				Application = new InternalWPFApplication { ShutdownMode = ShutdownMode.OnExplicitShutdown };

				Application.Resources.MergedDictionaries.Add(Application.LoadComponent(new Uri(pathToLaunchResourceDictionary, UriKind.RelativeOrAbsolute)) as ResourceDictionary);

				AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

				if (forceSoftwareOnlyRendering) // Force WPF to render in software mode. Usually, this would be due to interacting with another DirectX application (like screen-warping software in a flight simulator).
					RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

				ApplicationName = ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(executingAssembly, typeof(AssemblyTitleAttribute))).Title;
				CompanyName = ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute(executingAssembly, typeof(AssemblyCompanyAttribute))).Company;
				ApplicationDataFolderPath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), CompanyName), ApplicationName);
				ExecutablePath = Path.GetDirectoryName(executingAssembly.Location);
				if (ExecutablePath == null)
					throw new GeneralException("Unexpected null execution path.", null);
				if (ExecutablePath.Substring(0, 6) == "file:\\")
					ExecutablePath = ExecutablePath.Substring(6);
				ApplicationFileVersionInfo = FileVersionInfo.GetVersionInfo(executingAssembly.Location);
				ApplicationVersion = executingAssembly.GetName().Version;

				if (!Directory.Exists(ApplicationDataFolderPath))
					Directory.CreateDirectory(ApplicationDataFolderPath);

				logger = new LoggerZip(Path.Combine(ApplicationDataFolderPath, "Log"));
			}
			catch (Exception exception) {
				Cleaner.CleanConstructor(this, exception);
			}
		}

		public bool ForceSoftwareOnlyRendering { get; private set; }

		public Version ApplicationVersion { get; }

		public string GetLogFileAsString
		{
			get
			{
				return logger.GetLogFileAsString();
			}
		}

		//public AboutBoxRequestedDelegate AboutBoxRequested {
		//	get {
		//		return AboutBoxRequestedHandler;
		//	}
		//}

		public Application Application { get; private set; }

		public string ApplicationDataFolderPath { get; }

		public FileVersionInfo ApplicationFileVersionInfo { get; }

		public string ApplicationName { get; }

		public string CompanyName { get; }

		public string ExecutablePath { get; }

		public Assembly ExecutingAssembly { get; private set; }

		public void ReportMessage(string publicMessage, string privateMessageOrNull, MessageType messageType) {
			logger.AddMessage(publicMessage, privateMessageOrNull);

			if (messageType == MessageType.Display)
				MessageBox.Show(publicMessage + "\n\n" + privateMessageOrNull, ApplicationName);
			else
#if DEBUG
				Debug.WriteLine(publicMessage + "\n\n" + privateMessageOrNull);
#endif
		}

		public void ShowAboutBox() {
			try {
				MessageBox.Show(ApplicationName + "\n\nVersion " + ApplicationVersion.Major + "." + ApplicationVersion.Minor + "." + ApplicationVersion.Build, "About " + ApplicationName, MessageBoxButton.OK, MessageBoxImage.Information);
			}
			catch (Exception exception) {
				MessageBox.Show(exception.Message);
			}
		}

		public string FilePathWithFileExtension
		{
			get
			{
				return logger.FilePathWithFileExtension;
			}
		}

		public void AddMessage(string publicMessage, string privateMessageOrNull) {
			logger.AddMessage(publicMessage, privateMessageOrNull);
		}

		string ILogger.GetLogFileAsString() {
			return logger.GetLogFileAsString();
		}

		public string GetLogFilePath(DateTime anyUniversalTime) {
			return logger.GetLogFilePath(anyUniversalTime);
		}

		public void Dispose() {
			using (var cleaner = new Cleaner()) {
				cleaner.DisposeObject(ref logger);

				ExecutingAssembly = null;
				Application = null;
			}
		}

		public void Run() {
			Application.Run();
		}

		public void Shutdown(int exitCode) {
			((InternalWPFApplication)Application).SendExitingEvent();

			Application.Shutdown(exitCode);
		}

		static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
#if DEBUG
			Debugger.Break(); // Who forgot to catch an exception?
#endif

			var exception = e.ExceptionObject as Exception;
			MessageBox.Show(exception == null ? "An unknown error occurred." : "An unhandled error occurred requiring the application to exit: " + exception.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
		}

		/// <summary>
		///     See http://elegantcode.com/2011/03/02/wpf-advanced-jump-lists-using-a-single-instance-application/
		///     for how to make WPF a single-instance app. Basically, make app.xaml compile as a Page, paste this Main
		///     and event handler in here, implement ISingleInstanceApp in App, and set app startup in project properites to the
		///     App.
		/// </summary>
		internal class InternalWPFApplication : Application, ISingleInstanceApp {
			public delegate void ExitingDelegate(InternalWPFApplication sender, EventArgs eventArgs);

			/// <summary>
			///     This runs when you try to launch a second instance of the app.
			/// </summary>
			public bool SignalExternalCommandLineArgs(IList<string> args) {
				// handle command line arguments of second instance
				// ...

				return true;
			}

			public event ExitingDelegate Exiting;

			internal void SendExitingEvent() {
				if (Exiting != null)
					Exiting(this, new EventArgs());
			}
		}
	}
}
